﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;


namespace BoilenEditor.Primitives {

    [DebuggerDisplay( "FileContents: FilePath={FilePath}" )]
    public partial class FileContents : DependencyObject {

        // <#@ include file="../../Templates/Boilen.tt" #>
        // <#@ include file="./Includes.tt" #>
        // <#@ include file="./Data.txt" #>
        private static readonly Regex IncludeFile = new Regex( @"<#@\s*include\s+file\s*=\s*""([^""]+\.tx?t)""\s*#>", RegexOptions.Compiled | RegexOptions.IgnoreCase );

        private static readonly Regex StringLeftParenthesis = new Regex( @"""[^""]*\([^""]*""", RegexOptions.Compiled );
        private static readonly Regex StringRightParenthesis = new Regex( @"""[^""]*\)[^""]*""", RegexOptions.Compiled );
        private const string EscapedLeftParenthesis = "~![";
        private const string EscapedRightParenthesis = "]!~";

        private static readonly Regex LeftParenthesis = new Regex( @"\(\s*([^\s])", RegexOptions.Compiled );
        private static readonly Regex RightParenthesis = new Regex( @"([^\s])\s*\)", RegexOptions.Compiled );
        private static readonly Regex TypeofParenthesis = new Regex( @"typeof\(([^\)]+)\)", RegexOptions.Compiled );
        private static readonly Regex KeywordParenthesis = new Regex( @"(for|foreach|if|switch|typeof|while)\s*\(", RegexOptions.Compiled );
        private static readonly KeyValuePair<Regex, MatchEvaluator>[] Formatters = new[] {
            CreateFormatter( StringLeftParenthesis, m => GetCaptureValue( m, 0 ).Replace( "(", EscapedLeftParenthesis ) ),
            CreateFormatter( StringRightParenthesis, m => GetCaptureValue( m, 0 ).Replace( ")", EscapedRightParenthesis ) ),
            CreateFormatter( LeftParenthesis, m => "( " + GetCaptureValue( m, 1 ) ),
            CreateFormatter( RightParenthesis, m => GetCaptureValue( m, 1 ) + " )" ),
            CreateFormatter( KeywordParenthesis, m => GetCaptureValue( m, 1 ) + "(" ),
            CreateFormatter( TypeofParenthesis, m => "typeof(" + GetCaptureValue( m, 1 ).Trim( ) + ")" ),
        };

        private string originalContent_ = "";


        partial void InitializeInstance( ) {
            this.Read( );
            this.UpdateStatusName( );
        }


        public string FilePath { get { return this.FileData.FilePath; } }


        public void Read( ) {
            try {
                this.IsReading = true;

                if( File.Exists( this.FilePath ) ) {
                    string lastTokenLine = "";
                    string fileExtension = Path.GetExtension( this.FilePath );
                    bool fullFormat = fileExtension == ".tt";
                    string[] lines = File.ReadAllLines( this.FilePath, Encoding.UTF8 );
                    this.Content = lines.Aggregate(
                        new StringBuilder( ),
                        ( sb, line ) => sb.Append( FormatLine( line, fullFormat, ref lastTokenLine ) ).Append( Environment.NewLine ),
                        ( sb ) => sb.Replace( "\t", "    " ).ToString( )
                    );
                }
                else {
                    this.Content = "";
                }
            }
            finally {
                this.IsReading = false;
            }

            this.UpdateOriginalContent( );
        }

        public IEnumerable<string> GetReferences( ) {
            string[] filteredLines = this.Content.Split( new[] { Environment.NewLine }, StringSplitOptions.None );
            var references = new List<string>( );
            FilterContent( filteredLines, references );

            return references;
        }

        public IEnumerable<string> SaveShadow( string shadowPath ) {
            string[] filteredLines = this.Content.Split( new[] { Environment.NewLine }, StringSplitOptions.None );
            var references = new List<string>( );
            FilterContent( filteredLines, references );

            File.WriteAllLines( shadowPath, filteredLines );
            return references;
        }

        private static void FilterContent( string[] lines, List<string> references ) {
            for( int i = 0; i < lines.Length; ++i ) {
                string line = lines[i];
                Match match = IncludeFile.Match( line );

                if( match.Success ) {
                    string reference = GetCaptureValue( match, 1 );
                    references.Add( reference );
                    string fileName = Path.GetFileName( reference );
                    lines[i] = line.Replace( reference, fileName );
                }
            }
        }

        private void SaveCore( string path ) {
            string oldContent = Ex.ReadFile( path );
            if( this.Content != oldContent && this.Content != oldContent + Environment.NewLine )
                File.WriteAllText( path, this.Content, Encoding.UTF8 );

            this.UpdateOriginalContent( );
        }

        private string BuildSaveMessage( Exception ex ) {
            if( ex is UnauthorizedAccessException )
                return string.Format(
                    "Could not save {1}:{0}{0}{2}:{0}{3}{0}{0}Check that the file has been checked out of source control.",
                    Environment.NewLine, Path.GetFileName( this.FilePath ), ex.GetType( ).FullName, ex.Message
                );

            return null;
        }

        public bool Save( ) {
            return Ex.Retry( this.FilePath, SaveCore, "File Save Failure", BuildSaveMessage );
        }


        private void UpdateOriginalContent( ) {
            this.originalContent_ = this.Content;
            this.UpdateIsModified( );
        }

        private void UpdateIsModified( ) {
            if( !this.IsReading )
                this.IsModified = !this.originalContent_.Equals( this.Content );
        }

        private void UpdateStatusName( ) {
            this.StatusName = this.FileData.Name + (this.IsModified ? "*" : "");
        }

        private static string FormatLine( string line, bool fullFormat, ref string lastTokenLine ) {
            string trimmedLine = line.TrimEnd( );

            if( string.IsNullOrEmpty( trimmedLine ) )
                return line;

            string formattedLine = trimmedLine;
            if( fullFormat ) {
                if( line.Contains( '#' ) )
                    lastTokenLine = line;

                if( lastTokenLine.StartsWith( "<#" ) && !formattedLine.StartsWith( "<#@" ) ) {
                    string escapedFormattedLine = Formatters.Aggregate(
                        formattedLine.Replace( "((", "( (" ).Replace( "))", ") )" ),
                        ( l, f ) => f.Key.Replace( l, f.Value )
                    );

                    formattedLine = escapedFormattedLine
                        .Replace( EscapedLeftParenthesis, "(" )
                        .Replace( EscapedRightParenthesis, ")" );

                    if( line.EndsWith( "#>" ) )
                        lastTokenLine = "";
                }
            }

            return formattedLine;
        }

        private static string GetCaptureValue( Match match, int group ) {
            string firstCapture = match.Groups[group].Captures[0].Value;
            return firstCapture;
        }

        private static KeyValuePair<Regex, MatchEvaluator> CreateFormatter( Regex regex, MatchEvaluator formatter ) {
            return new KeyValuePair<Regex, MatchEvaluator>( regex, formatter );
        }

        private void OnIsModifiedChanged( ) {
            this.UpdateStatusName( );
        }

        private static void ContentPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
            var self = (FileContents)d;
            self.UpdateIsModified( );
            self.OnContentChanged( EventArgs.Empty );
        }

    }

}
